﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MapAppLearning.RouteService;

namespace MapAppLearning
{
    class Genetics
    {

        #region Private fields

        /// <summary>
        /// This array holds the population that the algorithm will be working with
        /// </summary>
        StopLocal[,] geneArray;

        double[,] shortestDistanceArray;

        /// <summary>
        /// This is where information about the stops are stores.
        /// </summary>
        StopLocal[] mapNodes;

        /// <summary>
        /// The size of the population used by the simulation.
        /// </summary>
        int popSize;

        /// <summary>
        /// The maximum amount of buses allowed in a solution.
        /// </summary>
        int numBuses;

        /// <summary>
        /// The number of stops.
        /// </summary>
        int nodes;

        /// <summary>
        /// The maximum amount of passengers allowed on a bus.
        /// </summary>
        int passengerLimit;

        /// <summary>
        /// The maximum amount of time a student is allowed to travel.
        /// </summary>
        double timeLimit;

        //Dear Andrew: do we need this?
        //We keep track of where the best element is,
        //we can keep track of their fitnesses individually and look up what it is.
        /// <summary>
        /// The best known fitness score.
        /// </summary>
        int bestScore;

        public int BestScore
        {
            get { return bestScore; }
            set { bestScore = value; }
        }

        /// <summary>
        /// The index where the fittest member of the population is stores.
        /// </summary>
        int bestIndex;

        int seed;

        /// <summary>
        /// The map passed to it
        /// </summary>
        Map currentMap;

        #endregion

        #region Construction and Initialization

        //general constructor
        /// <summary>
        /// Builds a new Genetics object
        /// </summary>
        /// <param name="popSize">The size of the population to be maintained</param>
        /// <param name="numBuses">Maximum number of buses allowed</param>
        /// <param name="numNodes">Number of bus stops</param>
        /// <param name="passengerLimit">The maximum amount of passengers allowed on a bus</param>
        /// <param name="timeLimit">The longest one student is allowed to ride a bus.</param>
        public Genetics(int popSize, int numBuses, int numNodes, int passengerLimit, int timeLimit, Map mapIn)
        {
            //Allocate the population's memory
            geneArray = new StopLocal[popSize, numNodes];
            for (int i = 0; i < popSize; i++)
            {
                for (int h = 0; h < numNodes; h++)
                {
                    geneArray[i, h] = new StopLocal(0);
                }
            }

            //Allocate memory for the array of stops
            mapNodes = new StopLocal[numNodes];

            //Allocate memory for the shortestdistance matrix
            shortestDistanceArray = new double[numNodes, numNodes];

            //Set class fields to their respective parameter values
            this.popSize = popSize;
            this.numBuses = numBuses;
            this.nodes = numNodes;
            this.passengerLimit = passengerLimit;
            this.timeLimit = timeLimit;
            this.currentMap = mapIn;
            this.seed = 1;

            //bestScore = 25;
        }

        //MUSTDO set the gene array to reference nodes in the nodelist, that way we can go through the node list in one pass
        public void initializeGeneArray()
        {
            Random rand = new Random(seed);
            LinkedList<int> stopOrder = new LinkedList<int>();
            Random ran = new Random(seed);

            for (int i = 0; i < popSize; i++)
            {
                for (int r = 0; r < nodes; r++)
                {
                    int temp = r;
                    stopOrder.AddFirst(temp);
                }
                for (int j = 0; j < nodes; j++)
                {
                    int nextInd = ran.Next(stopOrder.Count);
                    int next = stopOrder.ElementAt(nextInd);
                    stopOrder.Remove(next);
                    geneArray[i, j].Here = mapNodes[next].Here;
                    geneArray[i, j].Id = mapNodes[next].Id;
                    geneArray[i, j].Passengers = mapNodes[next].Passengers;
                    geneArray[i, j].BusId = ran.Next(numBuses);
                }
            }
        }

        #endregion

        #region Accessors

        [Obsolete]
        public void setMap(StopLocal[] map)
        {
            mapNodes = map;
        }

        [Obsolete]
        /// <summary>
        /// Use Map instead.
        /// </summary>
        /// <param name="map"></param>
        /// <returns></returns>
        public StopLocal[] getMap(StopLocal[] map)
        {
            return mapNodes;
        }

        /// <summary>
        /// The matrix of costs in time for movements between stops.
        /// </summary>
        public double[,] ShortestDistanceArray
        {
            get { return shortestDistanceArray; }
            set { shortestDistanceArray = value; }
        }

        //public Stop[,] getGenePool()
        //{
        //    return geneArray;
        //}

        [Obsolete]
        /// <summary>
        /// Deprecated. Use ShortestDistanceArray property instead.
        /// </summary>
        /// <param name="matrix"></param>
        public void setShortestDistanceArray(double[,] matrix)
        {
            ShortestDistanceArray = matrix;
        }

        #endregion

        #region Simulator Procedures

        //MUSTDO mutation
        public void mutatePopulation()
        {
            Random rand = new Random(seed);
            int nodeNum = 0;
            int nextVal = 0;

            for (int i = 2; i < popSize; i++)
            {
                int willMutate = rand.Next(100);
                if (willMutate >= 90)
                {
                    nodeNum = rand.Next(nodes);
                    nextVal = rand.Next(numBuses);
                    geneArray[i, nodeNum].BusId = nextVal;
                }
                willMutate = rand.Next(100);
                if (willMutate >= 90)
                {
                    nodeNum = rand.Next(nodes);
                    nextVal = rand.Next(nodes);
                    StopLocal temp = geneArray[i, nodeNum];
                    geneArray[i, nodeNum] = geneArray[i, nextVal];
                    geneArray[i, nextVal] = temp;
                }
            }
        }


        public double[,] getBestFitBusAndTimeCounts()
        {
            double[,] results = new double[2,nodes];

            this.testFitnessAll();

            Bus[] busArray = new Bus[numBuses];
            for (int i = 0; i < numBuses; i++)
            {
                busArray[i] = new Bus(1);
            }

            for (int i = 0; i < numBuses; i++)
            {

            }
            double timeAccumulator = 0;
            //double totalTime = 0;
            int timeViolations = 0;
            double passengersOver = 0;
            //int busesUsed = 0;
            double distance = 0;
            for (int i = 0; i < nodes; i++)
            {
                int currentBus = geneArray[bestIndex, i].BusId;
                distance = shortestDistanceArray[busArray[currentBus].lastStop, geneArray[bestIndex, i].Id];
                timeAccumulator += distance;

                busArray[currentBus].lastStop = geneArray[bestIndex, i].Id;
                
                busArray[currentBus].timeOn += distance;
                
                busArray[currentBus].passengers += geneArray[bestIndex, i].Passengers;
            }

            for (int i = 0; i < numBuses; i++)
            {
                //last stop back to school:
                distance = shortestDistanceArray[busArray[i].lastStop, 0];
                timeAccumulator += distance;
                busArray[i].timeOn += distance;

                results[0,i] = busArray[i].passengers;
                results[1, i] = busArray[i].timeOn;
            }


            return results;
        }
        //MUSTDO test fitness of pop
        public int[] testFitnessAll()
        {
            //Used to preserve the champion
            this.bestScore = 0;
            this.bestIndex = 0;
            int[] fitnessArray = new int[popSize+1];
            fitnessArray[popSize] = 0;
            Bus[] busArray = new Bus[numBuses];
            for (int i = 0; i < numBuses; i++)
            {
                busArray[i] = new Bus(1);
            }
            //loop through accumulating distances for the buses and getting passengers
            for (int a = 0; a < popSize; a++)
            {
                //MUSTDO calculate total fitness based on bus capacity and total distance traveled
                //accumulating fitness values

                for (int i = 0; i < numBuses; i++)
                {
                    busArray[i].lastStop = 0;
                    busArray[i].timeOn = 0;
                    busArray[i].passengers = 0;
                }

                double fitnessValue = 0;
                double timeAccumulator = 0;
                //double totalTime = 0;
                int timeViolations = 0;
                double passengersOver = 0;
                //int busesUsed = 0;
                double distance = 0;
                for (int i = 0; i < nodes; i++)
                {
                    int currentBus = geneArray[a, i].BusId;
                    distance = shortestDistanceArray[busArray[currentBus].lastStop, geneArray[a, i].Id];
                    timeAccumulator += distance;

                    busArray[currentBus].lastStop = geneArray[a, i].Id;
                    if (i != 0)
                    {
                        busArray[currentBus].timeOn += distance;
                    }
                    busArray[currentBus].passengers += geneArray[a, i].Passengers;
                }

                for (int i = 0; i < numBuses; i++)
                {
                    //last stop back to school:
                    distance = shortestDistanceArray[busArray[i].lastStop, 0];
                    timeAccumulator += distance;
                    busArray[i].timeOn += distance;

                    if (busArray[i].passengers > passengerLimit)
                    {
                        passengersOver += 1;
                    }
                    if (busArray[i].timeOn > timeLimit)
                    {
                        timeViolations += 1;
                    }
                }
                //START FITNESS CALC
                fitnessValue = timeAccumulator;

                fitnessValue = 100000000 / fitnessValue;
                if (passengersOver > 0)
                {
                    fitnessValue = 1;
                }
                /*if (totalTime > 100)
                {
                    fitnessValue = 0;
                }*/
                //for time violatoins
                if (timeViolations > 0)
                {
                    fitnessValue = 1;
                }

                fitnessValue -= 500;

                if (fitnessValue <= 0)
                {
                    fitnessValue = 1;
                }
                fitnessArray[a] = (int)fitnessValue;
                if (fitnessValue > bestScore)
                {
                    bestScore = (int)fitnessValue;
                    bestIndex = a;
                    fitnessArray[popSize] = 1;
                }
            }
            //check for timeAccum
            return fitnessArray;
        }

        public int run(int generations)
        {
            int[] fitnesses = new int[this.popSize+1];
            for (int i = 0; i < generations; i++)
            {
                
                this.mutatePopulation();
                fitnesses = this.testFitnessAll();
                this.repopulatePool(fitnesses);

            }
            if (fitnesses[popSize] == 1)
            {
                return 1;
            }
            else return 0;
        }

        public void setRoutes()
        {
            this.testFitnessAll();
            List<RouteResponse[]>  tempRoutes= new List<RouteResponse[]>();
            for (int i = 0; i < numBuses; i++)
            {
                int index = 0;
                int lastStop = 0;
                RouteResponse[] result = new RouteResponse[nodes+1];
                for (int d = 0; d < nodes+1; d++)
                {
                    result[d] = this.currentMap.Routes[0, 0];
                }

                for (int j = 0; j < nodes; j++)
                {
                    if (this.geneArray[bestIndex, j].BusId == i)
                    {
                        result[index] = this.currentMap.Routes[lastStop, this.geneArray[bestIndex, j].Id];
                        index++;
                        lastStop = this.geneArray[bestIndex, j].Id;
                    }

                }
                result[index] = this.currentMap.Routes[lastStop, 0];
                tempRoutes.Add(result);
            }
            this.currentMap.ProducedByAlgorithm = tempRoutes;

        }

        //finds best index, reconstructs routes, return as array of routes.

        //MUSTDO repopulate based on fitness
        public void repopulatePool(int[] geneFitness)
        {

            int index = geneFitness.Length-1;
            int temp = 0;
            int temp2 = 0;
            for (int r = 0; r < geneFitness.Length-1; r++)
            {
                temp2 = geneFitness[r];
                if (temp2 < 1)
                {
                    temp2 = 1;
                }
                temp += temp2;

            }
            int next = 0;
            if (temp < 1)
            {
                temp = 1;
            }
            StopLocal[,] geneWheel = new StopLocal[(int)temp, nodes];
            StopLocal[,] geneTemp = (StopLocal[,])geneArray.Clone();

            for (int i = 0; i < index; i++)
            {
                temp2 = geneFitness[i];
                if (temp2 < 1)
                {
                    temp2 = 1;
                }
                
                for (int j = 0; j < geneFitness[i]; j++)
                {
                    
                    for (int a = 0; a < nodes; a++)
                    {
                        geneWheel[next, a] = new StopLocal(0);
                        geneWheel[next, a].Id = geneArray[i, a].Id;
                        geneWheel[next, a].BusId = geneArray[i, a].BusId;
                        geneWheel[next, a].Here = geneArray[i, a].Here;
                        geneWheel[next, a].Passengers = geneArray[i, a].Passengers;



                    }
                    next++;
                }
            }

            //repopulate
            int nextInd = -1;
            int mate = -1;
            int crossoverChance = -1;
            int crossoverLocation = -1;


            Random rand = new Random(seed);
            for (int r = 0; r < 2; r++)
            {
                for (int d = 0; d < nodes; d++)
                {
                    geneTemp[r, d].Passengers = geneArray[bestIndex, d].Passengers;
                    geneTemp[r, d].BusId = geneArray[bestIndex, d].BusId;
                    geneTemp[r, d].Here = geneArray[bestIndex, d].Here;
                    geneTemp[r, d].Id = geneArray[bestIndex, d].Id;



                }
            }
            for (int q = 2; q < index; q++)
            {
                if (temp == 0)
                {
                    temp = 1;
                }


                nextInd = rand.Next((int)temp);
                crossoverChance = rand.Next(100);
                if (crossoverChance < 98)
                {
                    for (int d = 0; d < nodes; d++)
                    {
                        geneTemp[q, d].Passengers = geneWheel[nextInd, d].Passengers;
                        geneTemp[q, d].BusId = geneWheel[nextInd, d].BusId;
                        geneTemp[q, d].Here = geneWheel[nextInd, d].Here;
                        geneTemp[q, d].Id = geneWheel[nextInd, d].Id;


                    }
                }
                else
                {
                    mate = rand.Next((int)temp);
                    crossoverLocation = (rand.Next(nodes));
                    for (int d = 0; d < nodes; d++)
                    {
                        geneTemp[q, d].Passengers = geneWheel[nextInd, d].Passengers;
                        geneTemp[q, d].BusId = geneWheel[nextInd, d].BusId;
                        geneTemp[q, d].Here = geneWheel[nextInd, d].Here;
                        geneTemp[q, d].Id = geneWheel[nextInd, d].Id;


                    }

                    for (int r = 0; r < crossoverLocation; r++)
                    {
                        geneTemp[q, r].BusId = geneWheel[nextInd, r].BusId;
                    }
                    for (int r = crossoverLocation; r < nodes; r++)
                    {
                        geneTemp[q, r].BusId = geneWheel[mate, r].BusId;
                    }



                }
            }
            this.geneArray = geneTemp;
        }

        #endregion

        //struct for buses
        public struct Bus
        {
            public int passengers;

            public double totalMiles;
            public Boolean startTime;
            public double timeOn;
            public int lastStop;

            [Obsolete]
            public Bus(int fail)
            {
                passengers = 0;
                totalMiles = 0;
                timeOn = 0;
                startTime = false;
                lastStop = 0;
            }

            public Bus(int passengers, int totalMiles, int timeOn, bool startTime, int lastStop)
            {
                this.passengers = passengers;
                this.totalMiles = totalMiles;
                this.timeOn = timeOn;
                this.startTime = startTime;
                this.lastStop = lastStop;
            }
        }

        //struct for vectors


    }
}

